package org.setamv.springcloud.hystrix.raw.command.circuitbreaker;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandMetrics;
import com.netflix.hystrix.HystrixMetrics;
import org.omg.PortableServer.THREAD_POLICY_ID;

import java.util.Objects;

/**
 * 测试Hystrix断路器打开的条件。<p>
 * 在未开启强制打开断路器的前提下，有两个配置项会影响断路器打开的条件：<ul>
 *      <li>circuitBreaker.requestVolumeThreshold，用于指定每个计时窗口内请求数量的阈值，比如10秒内达到20个请求才有可能打开断路器</li>
 *      <li>circuitBreaker.errorThresholdPercentage，用于指定每个计时窗口内请求发生错误的百分比阈值。比如10秒内请求错误数达到50%</li>
 * </ul>
 * 必须同时达到上面两个阈值才会开启断路器
 */
public class OpenConditionTestCommand {

    public static void main(String[] args) throws Exception {
        // 设置断路器打开的阈值，下面的设置为：如果在1.5秒内，请求数超过4个，请失败的几率超过50%，断路器就会打开，断路器打开后过1秒后又会重新打开
        // 指定每个计时窗口内请求数量的阈值为4
        ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.circuitBreaker.requestVolumeThreshold", 4);
        // 指定每个计时窗口内请求发生错误的百分比阈值为50%2)
        ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.circuitBreaker.errorThresholdPercentage", 5);
        // 指定断路器打开后，间隔多长时间断路器会重新关闭并执行请求。指定1秒钟。默认为5秒
        ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds", 1000);
        // 指定计时窗口的大小为1秒钟
        ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.metrics.rollingStats.timeInMilliseconds", 1500);

        // 下面的代码在执行完第20次后，短路器就会打开，并且第20次以后都不会再执行了
        long startTime = System.currentTimeMillis();
        boolean lastTimeCircuitBreakerOpened = false;
        OpenCircuitBreakerCommand command = null;
        for (int i = 1; i <= 18; i++) {
            command = new OpenCircuitBreakerCommand();
            String result = command.execute();
            long ellapsedTime = System.currentTimeMillis() - startTime;
            System.out.println("the " + i + " request result: " + result + ". ellapsed time: " + ellapsedTime + "(ms)");
            // 判断断路器状态是否与上一次断路器状态是否一致，如果不一致，表示要么从“通路”状态转换为“短路”状态；要么相反
            if (!Objects.equals(lastTimeCircuitBreakerOpened, command.isCircuitBreakerOpen())) {
                // 如果断路器从“通路”状态转换为“短路”状态
                if (command.isCircuitBreakerOpen()) {
                    System.out.println("circuit breaker is opened at the " + i + " request!!!!!\n");
                    startTime = System.currentTimeMillis();
                    lastTimeCircuitBreakerOpened = true;
                } else {
                    System.out.println("circuit breaker is closed at the " + i + " request!!!!!\n");
                    lastTimeCircuitBreakerOpened = false;
                }
                startTime = System.currentTimeMillis();
            }
            Thread.sleep(200L);
        }

        // 最后打印统计信息
        HystrixCommandMetrics metrics = command.getMetrics();
        StringBuilder sb = new StringBuilder();
        sb.append("CurrentConcurrentExecutionCount=").append(metrics.getCurrentConcurrentExecutionCount()).append("\n")
                .append("TotalRequests=").append(metrics.getHealthCounts().getTotalRequests()).append("\n")
                .append("ErrorCount=").append(metrics.getHealthCounts().getErrorCount()).append("\n")
                .append("ErrorPercentage=").append(metrics.getHealthCounts().getErrorPercentage());
        System.out.println(sb);
    }

    /**
     * 该命令将交替执行一个OK的请求，已经一个fallback请求，所以，如果总的访问数为偶数次，则fallback总会是50%。
     */
    public static class OpenCircuitBreakerCommand extends HystrixCommand<String> {
        private static int requestTimes = 0;
        public OpenCircuitBreakerCommand() {
            // 设置50毫秒超时
            super(HystrixCommandGroupKey.Factory.asKey("testGroup"), 50);
        }

        @Override
        protected String run() throws Exception {
            // 如果是偶数次访问，等待5秒，将导致超时回退，即访问失败
            if (requestTimes++ % 2 == 0) {
                Thread.sleep(5000);
            }
            return "ok";
        }

        @Override
        protected String getFallback() {
            if (isCircuitBreakerOpen()) {
                return "fallback by circuit breaker";
            } else {
                return "fallback by timeout";
            }
        }
    }
}
